;;;;
;;;; Common Lisp Condition System for XLISP-STAT 2.0
;;;; XLISP-STAT 2.1 Copyright (c) 1990, by Luke Tierney
;;;; Additions to Xlisp 2.1, Copyright (c) 1989 by David Michael Betz
;;;; You may give out copies of this software; for conditions see the file
;;;; COPYING included with this distribution.
;;;;


;; The condition system is used if the variable xlisp::*condition-hook* is
;; not nil. The internal functions xlerror and xlcerror, as well as the 
;; Lisp-callable C functions xerror, xcerror, xsignal, xwarn, xbreak and
;; xdebug, call the hook with the function symbol, the frame index and the
;; current environment as arguments. The environment can be used by evalhook
;; for evaluating things in the environment that was active when the
;; condition was signaled.
;;
;; If an image containing the condition system is saved, then the function
;; xlisp::use-conditions needs t be called on startup to enable the
;; condition system.

;; To do:
;; **** rethink handling of internal xlabort's
;; **** make full version of assert, etypecase, ctypecase, ecase, ccase
;; **** make remaining standard condition types

;; **** collect debugger features
;; **** different debugging tools -- get-value, set-value as alt to evalhook?
;; **** move around in stack frames?


(in-package "XLISP")

(require "common")


;;;;
;;;; Exported Symbols
;;;;

;; Signaling Conditions
(export '(error cerror signal *break-on-signals*))

;; Assertions
(export '(check-type assert))

;; Exhaustive Case Analysis
(export '(etypecase ctypecase ecase ccase))

;; Handling Conditions
(export '(handler-case ignore-errors handler-bind))

;; Defining and Creating Conditions
(export '(define-condition make-condition))

;; Establishing Restarts
(export '(with-simple-restart restart-case restart-bind))

;; Finding and manipulating Restarts
(export '(compute-restarts restart-name find-restart invoke-restart
	  invoke-restart-interactively))

;; Warnings
(export 'warn)

;; Restart Functions
(export '(abort continue muffle-warning store-value use-value))

;; Debugging Utilities
(export '(break invoke-debugger *debugger-hook*))

;; Predefined Condition Types
(export '(condition simple-condition serious-condition
	  error simple-error arithmetic-error division-by-zero
	  cell-error unbound-variable undefined-function
	  control-error file-error package-error program-error
	  stream-error end-of-file type-error simple-type-error
	  storage-condition warning simple-warning

	  simple-condition-format-string simple-condition-format-arguments
	  type-error-datum type-error-expected-type package-error-package
	  stream-error-stream file-error-pathname cell-error-name
	  arithmetic-error-operation arithmetic-error-operands))

;; Restart Data Type
(export 'restart)


;;;;
;;;; Public Variables
;;;;

(defvar *break-on-signals* nil)
(defvar *debugger-hook* nil)


;;;;
;;;; Internal Variables
;;;;


;; Unique Markers
(defvar *eof-mark* (gensym "EOF"))
(defvar *not-found* (gensym "NOT-FOUND"))

;; Handler Variables
(defvar *default-handler* nil)
(defvar *active-handlers* nil)

;; Restart Variables
(defvar *default-restart* nil)
(defvar *active-restarts* nil)
(defvar *continue-restarts* nil)
(defvar *condition-restarts* nil)

;; Debugger Variables
(defvar *debug-level* 0)
(defvar *debug-env* nil)
(defvar *debug-frame* nil)
(defvar *debug-print-length* nil)
(defvar *debug-print-level* nil)


;;;;
;;;; Initialization Function
;;;; (Must be called on each system startup, e. g. as a startup action)
;;;;

(defun use-conditions (&optional (reset nil))
  (setf *condition-hook* 'condition-hook)
  (setf *active-restarts* (list (list nil *default-restart*)))
  (setf *continue-restarts* *active-restarts*)
  (setf *active-handlers*
	(if *default-handler* (list (list nil t *default-handler*))))
  (if reset (top-level)))

(defun unuse-conditions ()
  (setf *condition-hook* nil)
  (top-level))


;;;;
;;;; Internal Restart Representation
;;;;

(defstruct (restart (:print-function print-restart))
  name
  function
  (test-function #'(lambda (c) (declare (ignore c)) t))
  interactive-function
  report-function)

(defun print-restart (restart stream depth)
  (declare (ignore depth))
  (if *print-escape*
      (format stream "#<Restart ~a: ~d>"
	      (restart-name restart)
	      (address-of restart))
    (let ((report (restart-report-function restart)))
      (if report
	  (funcall report stream)
	(let ((name (restart-name restart)))
	  (if name
	      (format stream "~a." name)
	    (error "can't print restart ~s without escapes"
		   restart)))))))

(setf *default-restart*
      (make-restart :name 'abort
		    :function
		      #'(lambda (&rest args)
			  (declare (ignore args))
			  (top-level nil))
		    :test-function #'(lambda (c) (declare (ignore c)) t)
		    :report-function
		      #'(lambda (s)
			  (format s "Return to Lisp Toplevel."))))


;;;;
;;;; Helper Functions for RESTART-CASE and RESTART-BIND
;;;;

(defun push-restarts (c reslist)
  (dolist (r (reverse reslist))
    (push (list c r) *active-restarts*)))

(defun expand-restart-binding-form (x)
  (let ((name (first x))
	(function (second x))
	(options (rest (rest x))))
    (unless function (error "restart form missing function - ~s" x))
    (when (and (null name) (not (getf options :report-function)))
	  (error "anonymous restart needs a report function - ~s"x))
    `(make-restart :name ',name :function ,function ,@options)))

(defun make-restart-case-parts (tagsym argsym cases)
  (let ((syms (mapcar #'(lambda (x) (gensym "GO")) cases))
	(head nil)
	(tail nil))
    (mapc #'(lambda (c s)
	      (push `(,(first c)
		      #'(lambda (&rest temp)
			  (setq ,argsym temp)
			  (go ,s))
		      ,@(transform-restart-case-options c))
		    head)
	      (push s tail)
	      (push `(return-from ,tagsym
				  (apply #'(lambda ,(second c)
					     ,@(restart-case-case-body c))
					 ,argsym))
		    tail))
	  cases
	  syms)
    (cons (nreverse head) (nreverse tail))))

(defun transform-restart-case-options (c)
  (let ((opts nil))
    (loop
     (setf c (rest (rest c)))
     (case (first c)
       (:report
	(push :report-function opts)
	(push (if (stringp (second c))
		  `(function (lambda (s) (format s "~a" ,(second c))))
		  `(function ,(second c)))
	      opts))
       (:test
	(push :test-function opts)
	(push `(function ,(second c)) opts))
       (:interactive
	(push :interactive-function opts)
	(push `(function ,(second c)) opts))
       (t (return (nreverse opts)))))))

(defun restart-case-case-body (c)
  (loop
   (setf c (rest (rest c)))
   (unless (member (first c) '(:report :test :interactive))
	   (return c))))

(defun condition-restarts (expr clist)
  (if (and (consp expr) (member (first expr) '(error cerror signal warn)))
      (mapcar #'second
	      (butlast *active-restarts*
		       (- (length *active-restarts*) (length clist))))))


;;;;
;;;; Internal Condition Representation
;;;;

(setf (get 'condition '*struct-slots*) nil)
(setf (get 'condition '*struct-print-function*) 'print-condition)

(defun transform-condition-report-option (x)
  (if x
      (if (stringp x) x `(function ,x))))

(defun plist-to-alist (plist)
  (do ((plist plist (rest (rest plist)))
       (alist nil (push (list (first plist) (second plist)) alist)))
      ((not (consp (rest plist))) (nreverse alist))))

(defun transform-condition-slot-options (spec)
  (let* ((name (if (consp spec) (first spec) spec))
	 (opts (if (consp spec) (plist-to-alist (rest spec)) nil))
	 (iform (assoc :initform opts)))
    (if iform
	(let ((form (second iform)))
	  `(cons
	    ',name
	    (cons
	     ,(if (constantp form)
		       `'(:initform ,form)
		       `(list :initform
			      (list 'eval
				    (list (function (lambda () ,form))))))
	     ',(remove-if #'(lambda (x) (eq (first x) :initform)) opts))))
        `'(,name ,@opts))))

(defun print-condition (c s d)
  (declare (ignore d))
  (let ((type (type-of c)))
    (if *print-escape*
	(format s "#<Condition ~s: ~d>" (type-of c) (address-of c))
        (let ((rep (get type '*condition-report*)))
	  (cond
	   ((null rep) (format s "~s" type))
	   ((stringp rep) (format s "~s" rep))
	   (t (funcall rep c s)))))))

(defun make-condition-class (name parent report doc slots)
  (setf (get name '*struct-print-function*) 'print-condition)
  (if parent (setf (get name '*struct-include*) parent))
  (setf (get name '*condition-report*)
	(if report report (get parent '*condition-report*)))
  (if (stringp doc) (setf (documentation name 'type) doc))
  (let ((old (if parent (mapcar #'copy-list (get parent '*struct-slots*))))
	(new nil))
    (dolist (s slots)
      (unless (assoc (first s) old) (push (list (first s) nil nil) new)))
    (let ((entries (append old (nreverse new))))
      (dolist (spec slots)
        (let* ((entry (assoc (first spec) entries))
	       (opts (rest spec))
	       (i (+ (position entry entries) 1)))
	  (dolist (opt opts)
	    (case (first opt)
	     (:reader
	      (setf (symbol-function (second opt))
		    (eval `(function (lambda (x) (%struct-ref x ,i))))))
	     (:writer
	      (setf (symbol-function (second opt))
		    (eval `(function (lambda (x v) (%struct-set x ,i v))))))
	     (:accessor
	      (setf (symbol-function (second opt))
		    (eval `(function (lambda (x) (%struct-ref x ,i)))))
	      (setf (get (second opt) '*setf*)
		    (eval `(function (lambda (x v) (%struct-set x ,i v))))))
	     (:initarg (push (second opt) (second entry)))
	     (:initform (setf (third entry) (second opt)))
	    ))))
      (setf (get name '*struct-slots*) entries)))
  name)

(defun initialize-condition (c info args)
  (do* ((i 1 (+ i 1))
	(info info (rest info))
	(vi (first info) (first info)))
      ((null info))
      (let* ((iargs (second vi))
	     (ival  *not-found*)
	     (iform (third vi)))
	(dolist (i iargs)
	  (setf ival (getf args i *not-found*))
	  (unless (eq ival *not-found*) (return)))
	(if (eq ival *not-found*)
	    (setf ival (if (constantp iform) iform (eval iform))))
	(%struct-set c i ival))))


;;;;
;;;; Helper Functions for HANDLER-BIND and HANDLER-CASE
;;;;      

(defun reverse-expand-handler-bind-forms (th)
  (let ((forms nil))
    (dolist (f th forms)
      (push `(list ',(first f) ,(second f)) forms))))

(defun push-condition-handlers (clist active)
  (dolist (ch clist)
    (push (cons active ch) *active-handlers*)))

(defun handler-case-handler-bind-forms (hforms varsym tagsyms)
  (mapcar #'(lambda (ht ts)
	      `(,(first ht) #'(lambda (temp) (setq ,varsym temp) (go ,ts))))
	  hforms
	  tagsyms))

(defun expand-handler-case-bodies (hforms bsym varsym tagsyms)
  (apply #'nconc
	 (mapcar #'(lambda (hf ts)
		     (let ((v (if (second hf) (first (second hf)) varsym)))
		       `(,ts
			 (return-from ,bsym
				      (let ((,v ,varsym))
					,@(rest (rest hf)))))))
		 hforms
		 tagsyms)))


;;;;
;;;; Hook Function and Lisp-Level Signaling Functions
;;;;

(defun handle-condition (c)
  (if (typep c *break-on-signals*)
      (with-simple-restart (continue "Proceed with signalling.")
        (format *debug-io* "~&Break on signal: ")
	(do-debugger c)))
  (dolist (he *active-handlers*)
    (let* ((*active-handlers* (first he))
	   (tspec (second he))
	   (h (third he)))
      (if (typep c tspec) (funcall h c)))))

(defun condition-argument (datum args &optional
				 (simple-type 'simple-error)
				 (type 'condition))
  (cond
   ((typep datum type) datum) ;;**** check for no additional args?
   ((symbolp datum) (apply #'make-condition datum args))
   ((stringp datum)
    (make-condition simple-type :format-string datum :format-arguments args))
   (t (error "bad condition arguments - ~s" (cons datum args)))))

(defun base-condition-hook (type *debug-frame* *debug-env* &rest args)
  (let ((*condition-hook* 'condition-hook))
    (case type
      (error (apply #'do-error args))
      (cerror (apply #'do-cerror args))
      (signal (apply #'do-signal args))
      (warn (apply #'do-warn args))
      (break (apply #'do-break args))
      (debug (apply #'do-debugger args)))))

(defun condition-hook (&rest args)
  (let ((*condition-hook* 'condition-hook))
    (handler-bind
     ((unbound-variable #'(lambda (c)
                      (autoload-variable (cell-error-name c))))
      (undefined-function #'(lambda (c)
                        (autoload-function (cell-error-name c)))))
     (apply #'base-condition-hook args))))

(defun do-error (datum &rest args)
  (let ((condition (condition-argument datum args)))
    (with-condition-restarts condition *condition-restarts*
      (setf *condition-restarts* nil)
      (handle-condition condition)
      (format *debug-io* "~&Error: ")
      (do-debugger condition))))

(defun do-cerror (cmsg datum &rest args)
  (let ((condition (condition-argument datum args)))
    (with-condition-restarts condition *condition-restarts*
      (setf *condition-restarts* nil)
      (restart-case
       (progn
	 (handle-condition condition)
	 (format *debug-io* "~&Error: ")
	 (do-debugger condition))
       (continue ()
	:report (lambda (s) (apply #'format s cmsg args))
	:test (lambda (c) (eq c condition)))))
      nil))

(defun do-signal (datum &rest args)
  (let ((condition (condition-argument datum args 'simple-condition)))
    (with-condition-restarts condition *condition-restarts*
      (setf *condition-restarts* nil)
      (handle-condition condition))
    nil))

(defun do-warn (datum &rest args)
  (let ((condition (condition-argument datum args 'simple-warning 'warning)))
    (with-condition-restarts condition *condition-restarts*
      (setf *condition-restarts* nil)
      (restart-case
       (progn
	 (signal condition)
	 (format *error-output* "~&Warning: ~a~%" condition))
       (muffle-warning ()
        :report "Muffle warning"
	:test (lambda (c) (eq c condition)))))
    nil))

(defun do-break (&optional (fmt-string "**BREAK**") &rest fmt-args)
  (with-simple-restart (continue "Return from BREAK.")
    (format *debug-io* "Break: ")
    (do-debugger
     (make-condition 'simple-condition
		     :format-string fmt-string
		     :format-arguments fmt-args)))
  nil)


;;;;
;;;; Debugger Functions
;;;;

(defun do-debugger (condition)
  ;; should probably check for a condition
  (when *debugger-hook*
	(let* ((hook *debugger-hook*)
	       (*debugger-hook* nil))
	  (funcall hook condition hook)))
  (let* ((*debug-level* (+ *debug-level* 1))
	 (current-level *debug-level*)
	 (*print-level* (if *debug-print-level*
			    *debug-print-level*
			    *print-level*))
	 (*print-length* (if *debug-print-length*
			    *debug-print-length*
			    *print-length*)))
    ;; print the error message
    (if condition
	(multiple-value-bind (val err)
			     (ignore-errors
			      (format *debug-io* "~a~%" condition))
	 (declare (ignore val))
	 (if err (format *debug-io* "~s~%" condition))))

    ;; flush the input buffer and reset the system internals
    (reset-system)

    ;; do the back trace
    (if *tracenable* (baktrace (if *tracelimit* *tracelimit* -1)))

    ;; read-eval-print loop
    (let ((*continue-restarts* (compute-restarts condition)))
      (loop
       (with-simple-restart (abort "Return to break level ~d." current-level)

	 (when *batch-mode* (format *debug-io* "uncaught error~%") (exit))

	 ;; print restart information (**** optional??)
	 (format *debug-io* "Break level ~d.~%" current-level)
	 (format
	  *debug-io*
	  "To continue, type (continue n), where n is an option number:~%")
	 (dotimes (i (length *continue-restarts*))
	   (multiple-value-bind
	    (val err)
	    (ignore-errors
	     (format *debug-io* "~2d: ~a~%" i (nth i *continue-restarts*)))
	    (declare (ignore val))
	    (if err 
		(ignore-errors
		 (format *debug-io* "~s~%" (nth i *continue-restarts*))))))

	 (loop
	  ;; print a prompt
	  (if (eq *package* (find-package "USER"))
	      (format *debug-io* "~&~d> " *debug-level*)
	      (format *debug-io* "~&~A ~d> "
		      (package-name *package*)
		      *debug-level*))

	  ;; read and save an input expression
	  (let ((expr (read *debug-io* nil *eof-mark*)))
	    (if (eq expr *eof-mark*) (continue 0)) ;;**** is this right??
	    (setf +++ ++ ++ + + - - expr))

	  ;; evaluate the expression, save and print the results
	  (let ((vals (multiple-value-list (evalhook - nil nil *debug-env*))))
	    (setf *** ** ** * * (first vals))

	    (fresh-line *debug-io*)

	    (dolist (v vals) (format *debug-io* "~s~%" v)))))))))

(defun debug-fun ()
  (if (or (null *debug-frame*) (null (stack-value *debug-frame*)))
      nil
      (stack-value (+ *debug-frame* 1))))

(defun clean-up (&optional c) (continue c))

(defun baktrace (&optional levels (print-args *baktrace-print-arguments*))
  (if *debug-frame*
      (do ((fp *debug-frame* (- fp (stack-value fp)))
	   (n (if levels levels -1) (- n 1)))
	  ((or (= n 0) (null (stack-value fp))))
	  (let ((p (+ fp 1)))
	    (format *error-output* "Function: ~s~%" (stack-value p))
	    (incf p)
	    (if print-args
		(let ((argc (stack-value p)))
		  (incf p)
		  (when (> argc 0)
			(format *error-output* "Arguments:~%")
			(dotimes (i argc)
			  (format *error-output* "  ~s~%"
				  (stack-value (+ p i))))))))))
  (values))

(defun show-bindings (&optional vars)
  (dolist (a (first *debug-env*))
    (if (consp a)
	(dolist (b a)
	  (if (consp b)
	      (let ((s (car b))
		    (v (cdr b)))
		(if (and (symbolp s)
			 (or (null vars)
			     (eq s vars)
			     (and (consp vars) (member s vars))))
		    (format *error-output* "~s~15t~s~%" s v)))))))
  (values))

(defmacro get-value (form) `(evalhook ,form nil nil *debug-env*))

(defmacro set-value (form val) `(evalhook (setf ,form ,val) *debug-env*))


;;;;
;;;; Public Interface
;;;;

(defun prompt-for (type fmt-string &rest fmt-args)
  (loop
   (apply #'format *debug-io* fmt-string fmt-args)
   (let ((val (eval (read *debug-io*))))
     (if (typep val type) (return val))
     (format *debug-io* "~s is not of type ~s.~%" val type))))
       
;;**** simple version -- use this in define-cmp-macro
(defun type-check (x spec)
  (unless (typep x spec) (error "~s is not of type ~s." x spec))
  nil)

#|
(defmacro check-type (place spec &optional string)
  `(type-check ,place ',spec))
|#

;; version of check-type that returns the final value of the place form
(defmacro base-check-type (place spec &optional string)
  (let ((valsym (gensym "VAL")))
    `(loop
      (let ((,valsym ,place))
	(if (typep ,valsym ',spec)
	    (return ,valsym)
	    (restart-case
	     (error 'check-type-error
		    :datum ,valsym
		    :expected-type ',spec
		    :form ',place
		    :type-string ,string)
	     (store-value (,valsym)
	      :report "Store new value."
	      :interactive (lambda ()
			     (list
			      (prompt-for ',spec "Value for ~s: " ',place)))
	      (setf ,place ,valsym))))))))

(defmacro check-type (place spec &optional string)
  `(progn (base-check-type ,place ,spec ,string)
	  nil))
     
;;**** simple versions
(defmacro assert (testform &optional places datum &rest args)
  (if datum
      `(unless ,testform (error ,datum ,@args))
      `(unless ,testform
	       (error "The assertion ~S failed" ',testform))))

(defmacro etypecase (var &rest forms)
  (let ((vsym (gensym "VAR")))
    `(let ((,vsym ,var))
       (typecase ,vsym
		 ,@forms
		 (t (error 'type-error
			   :datum ,vsym
			   :expected-type '(or ,@(mapcar #'first forms))))))))

(defmacro ctypecase (var &rest body)
  `(typecase (base-check-type ,var (or ,@(mapcar #'first body)))
	     ,@body))

(defun compute-case-match-type (cases)
  (let ((keys nil))
    (dolist (b cases (cons 'member (nreverse keys)))
      (if (consp (first b))
	  (dolist (k (first b)) (push k keys)))
	  (push (first b) keys))))

(defmacro ecase (var &rest forms)
  (let ((vsym (gensym "VAR")))
    `(let ((,vsym ,var))
       (case ,vsym
	     ,@forms
	     (t (error 'type-error
		       :datum ,vsym
		       :expected-type (compute-case-match-type ',forms)))))))

(defmacro ccase (var &rest body)
  `(case (base-check-type ,var ,(compute-case-match-type body))
	 ,@body))

(defmacro handler-case (expr &rest hforms)
  (if (eq (first (first (last hforms))) :no-error)
      (let ((errsym (gensym "ERROR"))
	    (normsym (gensym "NORMAL"))
	    (ne-form (first (last hforms)))
	    (e-forms (butlast hforms)))
	`(block ,errsym
	   (multiple-value-call #'(lambda ,@(rest ne-form))
	     (block ,normsym
	       (return-from ,errsym
	         (handler-case (return-from ,normsym ,expr)
		   ,@e-forms))))))
      (let ((bsym (gensym "BLOCK"))
	    (varsym (gensym "VAR"))
	    (tagsyms (mapcar #'(lambda (x) (gensym "TAG")) hforms)))
	`(block ,bsym
	   (let (,varsym)
	     (tagbody
	      (handler-bind
	       ,(handler-case-handler-bind-forms hforms varsym tagsyms)
	       (return-from ,bsym ,expr))
	      ,@(expand-handler-case-bodies hforms bsym varsym tagsyms)))))))

(defmacro ignore-errors (&rest forms)
  `(handler-case (progn ,@forms)
     (error (c) (values nil c))))

(defmacro handler-bind (th &rest body)
  (let ((valsym (gensym "VALUE")))  
    `(let ((*active-handlers* *active-handlers*))
       (push-condition-handlers (list ,@(reverse-expand-handler-bind-forms th))
				*active-handlers*)
       ;; this errset traps internal xlabort's (from stack overflows)
       ;; and converts them to calls to (abort)
       (let ((,valsym (errset (multiple-value-list (progn ,@body)) nil)))
	 (if ,valsym
	     (values-list (first ,valsym))
	     (error "stack overflow"))))))

#|
(defmacro handler-bind (th &rest body)
  `(let ((*active-handlers* *active-handlers*))
     (push-condition-handlers (list ,@(reverse-expand-handler-bind-forms th))
			      *active-handlers*)
     ,@body))
|#

(defmacro handler-bind (th &rest body)
  (let ((valsym (gensym "VALUE")))  
    `(let ((*active-handlers* *active-handlers*))
       (push-condition-handlers (list ,@(reverse-expand-handler-bind-forms th))
				*active-handlers*)
       ;; this errset traps internal xlabort's (from stack overflows)
       ;; and converts them to calls to (abort)
       (let ((,valsym (errset (multiple-value-list (progn ,@body)))))
	 (if ,valsym
	     (values-list (first ,valsym))
	     (error "stack overflow"))))))

(defmacro define-condition (name plist &optional slots &rest options)
  (if (< 1 (length plist)) (error "multiple inheritance not supported"))
  (if (null plist) (error "new conditions must inherit from an existing one"))
  `(make-condition-class ',name
			 ',(first plist)
			 ,(transform-condition-report-option
			   (second (assoc :report options)))
			 ',(second (assoc :documentation options))
			 (list ,@(mapcar #'transform-condition-slot-options
					 slots))))

(defun make-condition (type &rest args)
  (when (eq (get type '*struct-slots* *not-found*) *not-found*)
	(error "bad condition type - ~s" type))
  (let* ((info (get type '*struct-slots*))
	 (c (apply #'%make-struct type (make-list (length info)))))
    (initialize-condition c info args)
    c))

(defmacro with-simple-restart (rfa &rest forms)
  (let ((restart-name (first rfa))
	(format-string (second rfa))
	(format-args (rest (rest rfa))))
    `(restart-case (progn ,@forms)
       (,restart-name ()
         :report (lambda (stream) (format stream ,format-string ,@format-args))
	 (values nil t)))))

(defmacro restart-case (expr &rest cases)
  (let* ((tagsym (gensym "TAG"))
	 (argsym (gensym "ARGS"))
	 (valsym (gensym "VALS"))
	 (parts (make-restart-case-parts tagsym argsym cases)))
    `(block ,tagsym
       (let ((,argsym nil))
	 (tagbody
	  (restart-bind
	   ,(first parts)
	   (let ((*condition-restarts* (condition-restarts ',expr ',cases)))
	     (return-from ,tagsym ,expr)))
	  ,@(rest parts))))))

#|
(defmacro restart-bind (bds &rest body)
  (let ((valsym (gensym "VALUE")))
    `(let ((*active-restarts* *active-restarts*))
       (push-restarts nil (list ,@(mapcar #'expand-restart-binding-form bds)))
       ;; this errset traps internal xlabort's (from stack overflows)
       ;; and converts them to calls to (abort)
       (let ((,valsym (errset (multiple-value-list (progn ,@body)))))
	 (if ,valsym (values-list (first ,valsym)) (abort))))))
|#

(defmacro restart-bind (bds &rest body)
  `(let ((*active-restarts* *active-restarts*))
     (push-restarts nil (list ,@(mapcar #'expand-restart-binding-form bds)))
     ,@body))

(defmacro with-condition-restarts (condition rlist &rest forms)
  `(let ((*active-restarts* *active-restarts*))
     (push-restarts ,condition ,rlist)
     ,@forms))


(defun compute-restarts (&optional condition)
  (let ((result nil))
    (dolist (cr *active-restarts*)
      (if (restart-entry-applicable-p cr condition)
	  (push (second cr) result)))
    (nreverse (delete-duplicates result))))
	
;restart-name
(defun restart-entry-applicable-p (cr condition)
  (let ((c (first cr))
	(r (second cr)))
    (if c
	(eq c condition)
        (or (null condition)
	    (funcall (restart-test-function r) condition)))))
  
(defun find-restart (identifier &optional condition)
  (cond
   ((null identifier) nil)
   ((symbolp identifier)
    (second
     (find-if #'(lambda (x)
		  (and (restart-entry-applicable-p x condition)
		       (eq identifier (restart-name (second x)))))
	      *active-restarts*)))
   ((restart-p identifier)
    (second (find identifier *active-restarts* :key #'second)))))
   
	       
(defun invoke-restart (identifier &rest args)
  (let ((restart (find-restart identifier)))
    (if restart
	(apply (restart-function restart) args)
        (error "invalid restart - ~s" identifier))))

(defun invoke-restart-interactively (identifier)
  (let* ((restart (find-restart identifier))
	 (ifun (restart-interactive-function restart))
	 (rfun (restart-function restart)))
    (if restart
	(if ifun (apply rfun (funcall ifun)) (funcall rfun))
        (error "invalid restart - ~s" identifier))))


(defun abort (&optional condition)
  (invoke-restart (find-restart 'abort condition)))

(defun continue (&optional condition)
  (if (integerp condition)
      (let ((restart (nth condition *continue-restarts*)))
	(if restart (invoke-restart-interactively restart)))
      (let ((restart (find-restart 'continue condition)))
	(if restart (invoke-restart restart)))))

(defun muffle-warning (&optional condition)
  (invoke-restart (find-restart 'muffle-warning condition)))

(defun store-value (value &optional condition)
  (let ((restart (find-restart 'store-value condition)))
    (if restart (invoke-restart restart value))))

(defun use-value (value &optional condition)
  (let ((restart (find-restart 'use-value condition)))
    (if restart (invoke-restart restart value))))


;;;;
;;;; Condition Types
;;;;

(defun print-simple-condition (c s)
  (if *print-escape*
      (print-condition c s nil)
      (apply #'format
	     s
	     (simple-condition-format-string c)
	     (simple-condition-format-arguments c))))

(define-condition simple-condition (condition)
  ((format-string :accessor simple-condition-format-string
		  :initform "Simple condition."
		  :initarg :format-string)
   (format-arguments :accessor simple-condition-format-arguments
		     :initarg :format-arguments))
  (:report print-simple-condition))

(define-condition serious-condition (condition))
(define-condition error (serious-condition))

(define-condition simple-error (error)
  ((format-string :initform "Simple error." :initarg :format-string)
   (format-arguments :initarg :format-arguments))
  (:report print-simple-condition))

(define-condition warning (condition))

(define-condition simple-warning (warning)
  ((format-string :initform "Simple warning." :initarg :format-string)
   (format-arguments :initarg :format-arguments))
  (:report print-simple-condition))

(define-condition storage-condition (condition))

(define-condition cell-error (error)
  ((name :accessor cell-error-name :initarg :name))
  (:report "Cell error"))

(define-condition unbound-variable (cell-error)
  ()
  (:report
   (lambda (c s)
     (format s "The variable ~s is unbound." (cell-error-name c)))))

(define-condition undefined-function (cell-error)
  ()
  (:report
   (lambda (c s)
     (format s "The function ~s is not defined." (cell-error-name c)))))

(define-condition type-error (error)
  (format-string
   format-arguments
   (datum :initarg :datum :accessor type-error-datum)
   (expected-type :initarg :expected-type
		  :accessor type-error-expected-type))
  (:report
   (lambda (c s)
     (format s "~s is not of type ~s."
	     (type-error-datum c)
	     (type-error-expected-type c)))))

(define-condition check-type-error (type-error)
  ((form :initarg :form :accessor check-type-error-form)
   (type-string :initarg :type-string :accessor check-type-error-type-string))
  (:report
   (lambda (c s)
     (if (check-type-error-type-string c)
	 (format s "The value of ~s, ~s, is not ~a."
		 (check-type-error-form c)
		 (type-error-datum c)
		 (check-type-error-type-string c))
         (format s "The value of ~s, ~s, is not of type ~s."
		 (check-type-error-form c)
		 (type-error-datum c)
		 (type-error-expected-type c))))))

(define-condition simple-type-error (type-error))

#|
			arithmetic-error
				division-by-zero
			control-error
			file-error
			package-error
			program-error
			stream-error
				end-of-file

package-error-package

stream-error-stream
file-error-pathname

arithmetic-error-operation
arithmetic-error-operands
|#
